/**
 *  @file CircularBuffer.cpp
 */
#include "CircularBuffer.h"
#include "../exceptions/RuntimeException.h"
#include "../common/UtilFuncT.h"
#include "../text/CFString.h"

using namespace cppflib::exceptions;

namespace cppflib
{

namespace collections
{


CircularBuffer::CircularBuffer(void)
{
   this->validStartPos = 0;
}

CircularBuffer::CircularBuffer(sz_t reqSize)
{
   this->validStartPos = 0;
   __Realloc(reqSize, false);
}

CircularBuffer::~CircularBuffer(void)
{
}

void CircularBuffer::Realloc(sz_t reqSize) 
{
   throw RuntimeException(_S("Don't call Realloc from a CircularBuffer"));
}

sz_t CircularBuffer::ReserveSpace(sz_t reqSize)
{
   throw RuntimeException(_S("Don't call ReserveSpace from a CircularBuffer"));
   return 0;
}

u8_t * CircularBuffer::GetValidEnd() const
{
   throw RuntimeException(_S("Don't call GetValidEnd from a CircularBuffer"));
   return NULL;
}

u8_t * CircularBuffer::GetRawBuf() const
{
   throw RuntimeException(_S("Don't call GetRawBuf from a CircularBuffer"));
   return NULL;
}

bool CircularBuffer::MoveData2Front(sz_t startOff)
{
   throw RuntimeException(_S("Don't call MoveData2Front from a CircularBuffer"));
   return false;
}

////////////////////////////////////////////////////////////////////////////////////

/**
 *  @param [in] offset -- actual offset to internal buffer 
 *                        if offset is greater than buffer length, pointer returned will
 *                        be wrapped around
 */
u8_t * CircularBuffer::__GetBufferPtr(sz_t offset) const
{
   return  (offset >= this->len) ? pData + (offset % this->len) : pData + offset;
}

/**
 *  Set the valid length 
 */
void CircularBuffer::SetValidLength(sz_t validLen)
{
    this->validLen = cppflib::Min<sz_t>(validLen, this->len);
}


u8_t * CircularBuffer::__GetValidEnd() const
{
   return  __GetBufferPtr(validStartPos + validLen);
}

u8_t * CircularBuffer::__GetValidStart() const
{
   return pData + validStartPos;
}

void CircularBuffer::CopyData(const u8_t * pData, sz_t dataLen, bool isZeroTerm)
{
   sz_t reqLen = isZeroTerm ? dataLen + 1 : dataLen;
   int dataOffset = (reqLen > this->len) ? (int)(reqLen - this->len) : 0;

   CFString::Memcpy(this->pData, (pData + dataOffset), (dataLen - dataOffset));
   SetValidLength(dataLen - dataOffset);
   this->validStartPos = 0;
}

void CircularBuffer::AppendData(const u8_t * pData, sz_t dataLen, bool isZeroTerm)
{
   sz_t reqLen = isZeroTerm ? dataLen + 1 : dataLen;

   if (reqLen >= this->len) { // will surely replace the whole buffer
      CopyData(pData, dataLen, isZeroTerm);
   }
   else {
      u8_t * destPtr = __GetValidEnd();
      u8_t * pBufStart = __GetValidStart();
      bool isEndStepOnStart = false;

      // append data byte by byte
      for (sz_t i = 0; i < dataLen; ++i) {
         if (destPtr == pBufStart) isEndStepOnStart = true;
         *destPtr++ = *pData++;
         if (destPtr >= this->GetBufferEnd())
            destPtr = this->pData;
      }

      if (isZeroTerm) {
         if (destPtr >= this->GetBufferEnd())
            destPtr = this->pData;
         if (destPtr == pBufStart) isEndStepOnStart = true;
         *destPtr++ = '\0';
      }

      // update
      if (isEndStepOnStart && GetValidLength() > 0) {
         this->validStartPos = reinterpret_cast<ptr_t>(destPtr) - reinterpret_cast<ptr_t>(this->pData);
         if (this->validStartPos >= this->len)
            this->validStartPos = 0;
      }

      SetValidLength(GetValidLength() + dataLen);
   }
}

void CircularBuffer::ClearBuf()
{
   BinBuf::ClearBuf();
   this->validStartPos = 0;
}

/**
 *  Copy data from this circular buffer to user provided buffer
 *
 *  @param [in] destBuf -- pointer to buffer provided by user
 *  @param [in] destBufLen -- length of buffer
 *  @param [in] circularBufOffset -- offset of this circular buffer which starts reading
 *
 *  @return number of bytes copied
 */
sz_t CircularBuffer::GetData(u8_t * destBuf, sz_t destBufLen, sz_t circularBufOffset)
{
   if (circularBufOffset >= GetValidLength())
      return 0;

   sz_t actualLen = GetValidLength() - circularBufOffset;
   sz_t readLen = cppflib::Min<sz_t>(destBufLen, actualLen);

   u8_t * pBufStart = __GetBufferPtr(validStartPos + circularBufOffset);
   for (sz_t i = 0; i < readLen; ++i) {
      *destBuf++ = *pBufStart++;
      if (pBufStart >= this->GetBufferEnd())
         pBufStart = this->pData;
   }

   return readLen;
}


} // end of namespace collections


} // end of namespace cppflib

